#include "glProgram.h"
#include "IShader.h"


glProgram::glProgram()
{
}

glProgram::~glProgram()
{
}

void glProgram::RegisterVertexShader(std::shared_ptr<VertexShader> vertexShader)
{
    m_vertexShader = vertexShader;
    vertexShader->SetProgram(this);
    vertexShader->ConfirmVaryingVar();
}

void glProgram::RegisterFragmentShader(std::shared_ptr<FragmentShader> fragmentShader)
{
    m_fragmentShader = fragmentShader;
    fragmentShader->SetProgram(this);
}

void glProgram::Draw(TGAImage& target, float* zbuffer, const std::array<std::shared_ptr<VertexInfoBase>, 3>& vertexInfos)
{
	int width = target.get_width();
	int height = target.get_height();

    Vec3f viewport_coords[3];
	std::array<float, 3> vertexs_w;
    for (int i = 0;i < 3;i++)
    {
        // 顶点着色器返回投影坐标
		vertexInfos[i]->index = i;
        Vec4f coord = m_vertexShader->vertex(vertexInfos[i]);
		vertexs_w[i] = coord.w;
        // 透视除法
        coord = coord / coord.w;
        // 视口变换
        viewport_coords[i] = m_viewport * coord;
    }

	// 包围盒范围
	Vec2f bboxmin(width - 1, height - 1);
	Vec2f bboxmax(0, 0);
	bboxmin.x = std::min({ bboxmin.x, viewport_coords[0].x, viewport_coords[1].x, viewport_coords[2].x});
	bboxmin.y = std::min({ bboxmin.y, viewport_coords[0].y, viewport_coords[1].y, viewport_coords[2].y });
	bboxmax.x = std::max({ bboxmax.x, viewport_coords[0].x, viewport_coords[1].x, viewport_coords[2].x });
	bboxmax.y = std::max({ bboxmax.y, viewport_coords[0].y, viewport_coords[1].y, viewport_coords[2].y });
	m_fragmentShader->m_vertexInfos = vertexInfos;
	for (int x = bboxmin.x; x <= bboxmax.x; x++)
	{
		for (int y = bboxmin.y; y <= bboxmax.y; y++)
		{
			Vec3f P = Vec3f(x, y, 0);
			Vec3f bc_screen = glm::barycentricCorrect(
				Vec3f(viewport_coords[0].x, viewport_coords[0].y, vertexs_w[0]),
				Vec3f(viewport_coords[1].x, viewport_coords[1].y, vertexs_w[1]),
				Vec3f(viewport_coords[2].x, viewport_coords[2].y, vertexs_w[2]), P);
			if (bc_screen.x < 0 || bc_screen.y < 0 || bc_screen.z < 0) continue;
			if (x < 0 || x >= width || y < 0 || y >= height)
			{
				continue;
			}
			// 重心坐标插值
			P.z = bc_screen.x * viewport_coords[0].z + bc_screen.y * viewport_coords[1].z + bc_screen.z * viewport_coords[2].z;
			if (P.z < 0 || P.z > 1 || zbuffer[x + y * width] < P.z)
			{
				continue;
			}
			FragmentInfo info;
			info.bc_coord = bc_screen;
			info.position = P;
			bool bDiscard = m_fragmentShader->fragment(info);
			if (!bDiscard)
			{
				zbuffer[x + y * width] = P.z;
                target.set(x, y, info.color);
			}
		}
	}
}

void glProgram::SetViewPort(int width, int height)
{
    m_viewport = glm::viewport(width, height);
}
